
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * Clase AnalizadorSintactico
 * @author José Manuel Sánchez Bernabéu
 *         Mercedes Martinez Cortinas
 */
public class AnalizadorSintactico {
    /**
     * Analizador lexico
     */
    private AnalizadorLexico analex;

    /**
     * Token para saber que hacer en cada momento,
     * este token se buscara en los conjuntos de
     * prediccion de las diferentes reglas para
     * escoger aquella en la que se parezca
     */
    private Token preanalisis;
    
    public TablaSimbolos tsGlobal;
    public TablaSimbolos ts;
    public int nivelAmbito;
    public boolean usoConst;
    public int indice;
    /**
     * @return devuelve analex
     */
    public AnalizadorLexico getAnalex() {
        return analex;
    }

    /**
     * @param analex actualiza el valor de analex
     */
    public void setAnalex(AnalizadorLexico analex) {
        this.analex = analex;
    }

    /**
     * @return devuelve el token preanalisis
     */
    public Token getPreanalisis() {
        return preanalisis;
    }

    /**
     * @param preanalisis actualiza el valor de preanalisis
     */
    public void setPreanalisis(Token preanalisis) {
        this.preanalisis = preanalisis;
    }

    public String calcularSangrado()
    {
        String retorno="";

            // calculamos el sangrado
            if(nivelAmbito==1)
            {
                retorno+="    "; // 4 espacios
            }
            if(nivelAmbito==2)
            {
                retorno+="        "; // 8 espacios
            }
        return retorno;
    }
    /**
     * Constructor de la clase
     */
    public AnalizadorSintactico()
    {}

    /**
     * Constructor sobrecargado de la clase
     * @param analexAux Analizador lexico que se le pasa desde el main
     */
    public AnalizadorSintactico(AnalizadorLexico analexAux)
    {
        this.analex = analexAux;
        this.preanalisis = this.analex.siguienteToken();
        tsGlobal = new TablaSimbolos(null);
        this.nivelAmbito = 0;
        this.usoConst = false;
        this.indice = 0;
    }

    private void Empareja(int tipoToken)
    {
	//if(getPreanalisis().getTipo()==tipoToken) // si el tipo de token es el esperado
		setPreanalisis(getAnalex().siguienteToken()); // actualizamos el valor de preanalisis
	/*else
		ErrorSintactico( getPreanalisis(), Token.TipoTokenToString(tipoToken)); // El tipo no era el esperado*/
    }

    private void ErrorSintactico(Token encontrado, String seEsperaba)
	{
	    int columna;
	    columna=preanalisis.getColumna();

		if((preanalisis.getTipo() == Token.EOF))
			System.err.println("Error 4: encontrado final de fichero, esperaba "+ seEsperaba);
		else
			System.err.println("Error 3 ("+ preanalisis.getFila()+","+ columna +"): encontrado '"+encontrado.getLexema()+"', esperaba "+seEsperaba);

		System.exit(-1);
	}
    
    private void ErrorSemantico(Token encontrado, int tipoError)
            {
	    int columna;
	    columna=preanalisis.getColumna();
            switch(tipoError)
            {
                case 5: System.err.println("Error 5 ("+ preanalisis.getFila()+","+ columna +"): '"+encontrado.getLexema()+"' ya existe en este ambito");
                break;
                case 6: System.err.println("Error 6 ("+ preanalisis.getFila()+","+ columna +"): '"+encontrado.getLexema()+"' no ha sido declarado");
                break;
                case 7: System.err.println("Error 7 ("+ preanalisis.getFila()+","+ columna +"): '"+encontrado.getLexema()+"' no es una variable");
                break;
            }
            System.exit(-1);
	}

    /*public boolean FinalizaAnalisis() // 0 forzamos el fin del analisis
    {
        boolean retorno = false;

	if(getPreanalisis().getTipo()!=Token.EOF){
		ErrorSintactico(preanalisis,"'class' final de fichero				");
	}

	return retorno;
    }*/

    public final String S()
    {
        String Cs="", Ch="",Ss="";

        if(preanalisis.getTipo() == Token.CLASS)
	{
                nivelAmbito=0;
		Cs=C();
		Ss=Cs+"\n";
		Ss+=S();
		//Ss+="\n";
	}
	else
            if(preanalisis.getTipo()==Token.EOF)
            {
		Ss+="";
            }
            else
                ErrorSintactico(preanalisis,"'class' final de fichero");
        return Ss;
    }

    public final String C()
    {
        String Ds="", Dh="", Cs="", Daux="";

        Cs = "class ";
        Empareja(Token.CLASS);

        if(preanalisis.getTipo() == Token.ID)
	{
            
            if(tsGlobal.existe(preanalisis.getLexema()))
            {
                ErrorSemantico(preanalisis, 5);
            }
            else    
            {
                tsGlobal.nuevoSimbolo(preanalisis.getLexema(), Simbolo.CLASE, 0);
                ts = new TablaSimbolos(tsGlobal);
                nivelAmbito++;
                Cs+=preanalisis.getLexema()+" ";
                Empareja(Token.ID);
                if(preanalisis.getTipo() == Token.LLAVEI)
                {
                    Cs+="{\n";
                    Empareja(Token.LLAVEI);
                    Ds+=D();

                    // Ahora declaramos las variables que se han usado
                    Iterator it = ts.simbolos.entrySet().iterator();
                    Object []c = new Object[ts.simbolos.entrySet().toArray().length];
                    c=ts.simbolos.entrySet().toArray();
                    Object []b;
                    b=vectorOrdenado(c);
                    // Ahora empezamos a buscar por el ultimo.....
                    for(int i=0;i<b.length;i++)
                    {
                            Map.Entry e = (Map.Entry)b[i];
                            if(((Simbolo)e.getValue()).isEsUsada())
                                Daux+=calcularSangrado()+"double "+((Simbolo)e.getValue()).getNombre()+";\n";
                    }

                    // Buscare en la tabla las variables que habré usado
                    /*while(it.hasNext())
                    {
                            Map.Entry e = (Map.Entry)it.next();
                            if(((Simbolo)e.getValue()).isEsUsada())
                            Daux+=calcularSangrado()+"double "+((Simbolo)e.getValue()).getNombre()+";\n";
                    }*/

                    Cs+=Daux;

                    Cs+=Ds;

                    if(preanalisis.getTipo() == Token.LLAVED)
                    {
                        ts = null;
                        Cs+="}";
                        Empareja(Token.LLAVED);
                        ts = tsGlobal;
                        nivelAmbito--;
                    }
                    else
                        ErrorSintactico(preanalisis,"'}'");
                }
                else
                    ErrorSintactico(preanalisis,"'{'");
            }
        }
        else
            ErrorSintactico(preanalisis,"identificador");
        
        
        return Cs;
    }

    public final String D()
    {
        String Es="", Eh="", Ds="";

        if(preanalisis.getTipo() == Token.DOUBLE || preanalisis.getTipo() == Token.VOID)
        {
            Ds+=E();
            Ds+=D();
        }
        else
            if(preanalisis.getTipo() == Token.LLAVED)
            {
                Ds+="";
            }
            else
                ErrorSintactico(preanalisis,"'double' 'void' '}'");

        return Ds;
    }

    public final String E()
    {
        String Es="";

        if(preanalisis.getTipo() == Token.DOUBLE)
        {
            //Es+=V();
            V();
        }
        else
            if(preanalisis.getTipo() == Token.VOID)
            {
                Es+=M();
            }
            else
                ErrorSintactico(preanalisis,"'double' 'void'");

        return Es;
    }

    public final String V()
    {
        String Vs="";

        if(preanalisis.getTipo() == Token.DOUBLE)
        {
            Vs+=calcularSangrado()+"double ";
            Empareja(Token.DOUBLE);
            if(preanalisis.getTipo() == Token.ID)
            {
                if(!ts.existe(preanalisis.getLexema()))
                {
                    ts.nuevoSimbolo(preanalisis.getLexema(), Simbolo.VAR, 0,indice);
                    indice++;
                    Vs+=preanalisis.getLexema();
                    Empareja(Token.ID);
                    if(preanalisis.getTipo() == Token.PYC)
                    {
                        Vs+=";\n";
                    Empareja(Token.PYC);
                    }
                    else
                        ErrorSintactico(preanalisis,"';'");
                }
                else
                {
                    ErrorSemantico(preanalisis, 5);
                }
            }
            else
                ErrorSintactico(preanalisis,"identificador");
        }
        else
            ErrorSintactico(preanalisis,"'double'");
        return Vs;
    }

    public final String M()
    {
        String Ms="", Decls="", Cuerpos="", posibleConst="";

        if(preanalisis.getTipo() == Token.VOID)
        {
            Ms+=calcularSangrado()+"void ";
            Empareja(Token.VOID);
            if(preanalisis.getTipo() == Token.ID)
            {
                if(!ts.existe(preanalisis.getLexema()))
                {
                    ts.nuevoSimbolo(preanalisis.getLexema(), Simbolo.METODO, 0);
                    Ms+=preanalisis.getLexema();
                    Empareja(Token.ID);
                    if(preanalisis.getTipo() == Token.PARI)
                    {
                        Ms+=" (";
                        Empareja(Token.PARI);
                        if(preanalisis.getTipo() == Token.PARD)
                        {
                            Ms+=") ";
                            Empareja(Token.PARD);
                            if(preanalisis.getTipo() == Token.LLAVEI)
                            {
                                
                                Empareja(Token.LLAVEI);
                                ts = new TablaSimbolos(ts);
                                nivelAmbito++;
                                Decls=Decl();
                                Decls="";
                                
                                Cuerpos=Cuerpo();
                                
                                if(preanalisis.getTipo() == Token.LLAVED)
                                {
                                    if(!usoConst)
                                        Ms+="const ";
                                    Ms+="{\n";
                                    //Ms+=Decls;

                                    Iterator it = ts.simbolos.entrySet().iterator();
                                    Object []c = new Object[ts.simbolos.entrySet().toArray().length];
                                    c=ts.simbolos.entrySet().toArray();
                                    // ordenamos el vector por el indice
                                    Object []b;
                                    b=vectorOrdenado(c);
                                    // Ahora empezamos a buscar por el ultimo.....
                                    for(int i=0;i<b.length;i++)
                                    {
                                        Map.Entry e = (Map.Entry)b[i];
                                        if(((Simbolo)e.getValue()).isEsUsada())
                                            Decls+=calcularSangrado()+"double "+((Simbolo)e.getValue()).getNombre()+";\n";

                                    }


                                    // Buscare en la tabla las variables que habré usado
                                    /*while(it.hasNext())
                                    {
                                        Map.Entry e = (Map.Entry)it.next();
                                        if(((Simbolo)e.getValue()).isEsUsada())
                                            Decls+=calcularSangrado()+"double "+((Simbolo)e.getValue()).getNombre()+";\n";
                                    }*/
                                            
                                    Ms+=Decls;

                                    Ms+=Cuerpos;
                                    
                                    ts = ts.pop();
                                    nivelAmbito--;
                                    Ms+=calcularSangrado()+"}\n";
                                    Empareja(Token.LLAVED);

                                    usoConst = false; // Reiniciamos el valor de la variable
                                    //Desapilamos la tabla actual de la funcion
                                }
                                else
                                    ErrorSintactico(preanalisis,"'}'");
                            }
                            else
                                ErrorSintactico(preanalisis,"'{'");
                        }
                        else
                            ErrorSintactico(preanalisis,"')'");
                    }
                    else
                        ErrorSintactico(preanalisis,"'('");
                }
                else
                {
                    ErrorSemantico(preanalisis, 5);
                }
            }
            else
                ErrorSintactico(preanalisis,"identificador");
        }
        else
                ErrorSintactico(preanalisis,"void");

      return Ms;
    }

        public final String Decl()
        {
            String Decls="", Vs="";

            if(preanalisis.getTipo() == Token.DOUBLE)
            {
                Vs+=V();
                Decls+=Vs;
                Decls+=Decl();
            }
            else
                if(preanalisis.getTipo()==Token.ID || preanalisis.getTipo()==Token.LLAVED)
                {
                    Decls+="";
                }
                else
                    ErrorSintactico(preanalisis,"'double' '}' identificador");

            return Decls;
        }

        public final String Cuerpo()
        {
            String Cuerpos="", Vs="";

            if(preanalisis.getTipo() == Token.ID)
            {
                Cuerpos+=Instr();
                Cuerpos+=Cuerpo();
            }
            else
                if(preanalisis.getTipo() == Token.LLAVED)
                {
                    Cuerpos+="";
                }
                else
                    ErrorSintactico(preanalisis,"'identificador' '}'");

            return Cuerpos;
        }

        public final String Instr()
        {
            String Instrs="";

            if(preanalisis.getTipo() == Token.ID)
            {
                if(!ts.existe(preanalisis.getLexema()) && !ts.padre.existe(preanalisis.getLexema()) && !ts.padre.padre.existe(preanalisis.getLexema()))
                {
                    ErrorSemantico(preanalisis, 6);
                }
                else
                {
                    if(ts.busca(preanalisis.getLexema()).getTipoSimbolo()==Simbolo.CLASE || ts.busca(preanalisis.getLexema()).getTipoSimbolo()==Simbolo.METODO)
                    {
                        ErrorSemantico(preanalisis, 7);
                    }
                    else
                    {
                        // Si no existe en mi ambito pero si en el anterior y es una asignacion entonces pongo CONST delante de la funcion
                        if(ts.padre.existe(preanalisis.getLexema()) && !ts.existe(preanalisis.getLexema()))
                            usoConst=true;

                        Instrs+=calcularSangrado()+preanalisis.getLexema();
                        Empareja(Token.ID);
                        if(preanalisis.getTipo() == Token.ASIG)
                        {
                            Instrs+=" = ";
                            Empareja(Token.ASIG);
                            Instrs+=Factor();
                            if(preanalisis.getTipo() == Token.PYC)
                            {
                                Instrs+=";\n";
                                Empareja(Token.PYC);
                            }
                            else
                                ErrorSintactico(preanalisis,"';'");
                        }
                        else
                            ErrorSintactico(preanalisis,"'='");
                    }
                }
            }
	    else
		ErrorSintactico(preanalisis,"identificador");

            return Instrs;
        }

        public final String Factor()
        {
            String Factors="";

            if(preanalisis.getTipo() == Token.REAL)
            {
                Factors+=preanalisis.getLexema();
                Empareja(Token.REAL);
            }
            else
                if(preanalisis.getTipo() == Token.ID)
                {
                    if(!ts.existe(preanalisis.getLexema()) && !ts.padre.existe(preanalisis.getLexema()) && !ts.padre.padre.existe(preanalisis.getLexema()))
                    {
                        ErrorSemantico(preanalisis, 6);
                    }
                    else
                    {
                        if(ts.busca(preanalisis.getLexema()).getTipoSimbolo()==Simbolo.CLASE || ts.busca(preanalisis.getLexema()).getTipoSimbolo()==Simbolo.METODO)
                        {
                            ErrorSemantico(preanalisis, 7);
                        }
                        else
                        {
                            Factors+=preanalisis.getLexema();
                            Empareja(Token.ID);
                        }
                    }
                }
                else
                    ErrorSintactico(preanalisis,"identificador real");

            return Factors;
        }



        public Object[] vectorOrdenado(Object[] vector)
        {
            Object[] retorno = new Object[vector.length];
            Map.Entry aux;

            for(int i=0;i<vector.length;i++)
                retorno[i]=vector[i];

            // Hacemos el metodo de la burbuja
            for (int i=0;i<retorno.length;i++) //los arrays en Java empiezan en la posición 0 no en la 1.
            {
                for (int j=0;j<retorno.length-1;j++)
                {
                     if (((Simbolo)((Map.Entry)retorno[j]).getValue()).getIndice() > ((Simbolo)((Map.Entry)retorno[j+1]).getValue()).getIndice())
                     {
                         aux = (Map.Entry)retorno[j];
                         retorno[j] = retorno[j+1];
                         retorno[j+1] = aux;
                     }
                }
            }
               return retorno;
        }
}
